{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "318fed3e-2b96-4c63-bfa8-dd7d6fcf1c46",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:root:开始处理 ZIP 文件: C:/Users/20584/Desktop/0-9.zip\n",
      "INFO:root:处理完成，结果保存至: out/\n",
      "INFO:root:开始处理 ZIP 文件: C:/Users/20584/Desktop/0-9.zip\n",
      "INFO:root:处理完成，结果保存至: out/\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import cv2 as cv\n",
    "from zipfile import ZipFile\n",
    "from skimage import io\n",
    "import numpy as np\n",
    "import random\n",
    "import logging\n",
    "import yaml\n",
    "import tkinter as tk\n",
    "from tkinter import filedialog, ttk, messagebox\n",
    "from PIL import Image, ImageTk\n",
    "import threading\n",
    "\n",
    "logging.basicConfig(level=logging.INFO)\n",
    "\n",
    "def random_transform(img, scale_min=0.5, scale_max=1.5):\n",
    "    h, w = img.shape[:2]\n",
    "    scale_factor = random.uniform(scale_min, scale_max)\n",
    "    new_h, new_w = int(h * scale_factor), int(w * scale_factor)\n",
    "    \n",
    "    # 缩放图像\n",
    "    scaled_img = cv.resize(img, (new_w, new_h), interpolation=cv.INTER_LINEAR)\n",
    "    \n",
    "    # 中心裁剪或填充\n",
    "    if scale_factor >= 1:\n",
    "        y_start = (new_h - h) // 2\n",
    "        x_start = (new_w - w) // 2\n",
    "        cropped_img = scaled_img[y_start:y_start+h, x_start:x_start+w]\n",
    "    else:\n",
    "        cropped_img = np.zeros_like(img)\n",
    "        y_start = (h - new_h) // 2\n",
    "        x_start = (w - new_w) // 2\n",
    "        cropped_img[y_start:y_start+new_h, x_start:x_start+new_w] = scaled_img\n",
    "    \n",
    "    # 随机旋转（-45° ~ 45°）\n",
    "    angle = random.uniform(-45, 45)\n",
    "    M = cv.getRotationMatrix2D((w//2, h//2), angle, 1)\n",
    "    rotated_img = cv.warpAffine(cropped_img, M, (w, h), borderMode=cv.BORDER_REPLICATE)\n",
    "    \n",
    "    return rotated_img\n",
    "\n",
    "def process_images_from_zip(\n",
    "    zip_path, \n",
    "    scale_min=0.5, \n",
    "    scale_max=1.5, \n",
    "    num_images=10, \n",
    "    output_type='RGB',\n",
    "    progress_callback=None,\n",
    "    thread_event=None\n",
    "):\n",
    "    out_path = 'out/'\n",
    "    os.makedirs(out_path, exist_ok=True)\n",
    "    logging.info(f\"开始处理 ZIP 文件: {zip_path}\")\n",
    "    \n",
    "    with ZipFile(zip_path, 'r') as zipped:\n",
    "        image_files = [\n",
    "            name for name in zipped.namelist()\n",
    "            if name.lower().endswith(('.png', '.jpg', '.jpeg'))  # 仅处理JPEG/PNG\n",
    "        ]\n",
    "        total = len(image_files)\n",
    "        \n",
    "        for idx, name in enumerate(image_files):\n",
    "            if thread_event and thread_event.is_set():\n",
    "                logging.info(\"用户取消处理\")\n",
    "                return\n",
    "            \n",
    "            # 更新进度\n",
    "            if progress_callback:\n",
    "                progress_callback(int((idx + 1) / total * 100))\n",
    "            \n",
    "            with zipped.open(name) as file:\n",
    "                # 读取图像\n",
    "                img_bytes = file.read()\n",
    "                img = cv.imdecode(np.frombuffer(img_bytes, np.uint8), -1)\n",
    "                \n",
    "                # 处理输出类型\n",
    "                if output_type == 'Grayscale':\n",
    "                    img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)\n",
    "                elif output_type == 'Binary':\n",
    "                    img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)\n",
    "                    _, img = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)\n",
    "                \n",
    "                # 调整尺寸\n",
    "                img = cv.resize(img, (224, 224))\n",
    "                \n",
    "                # 保存原始处理结果\n",
    "                base_name = os.path.splitext(os.path.basename(name))[0]\n",
    "                save_path = os.path.join(out_path, f\"{base_name}_original.jpg\")\n",
    "                io.imsave(save_path, img)\n",
    "                \n",
    "                # 生成变换图像\n",
    "                for i in range(num_images - 1):\n",
    "                    transformed = random_transform(img, scale_min, scale_max)\n",
    "                    trans_path = os.path.join(out_path, f\"{base_name}_transformed_{i}.jpg\")\n",
    "                    io.imsave(trans_path, transformed)\n",
    "    \n",
    "    logging.info(f\"处理完成，结果保存至: {out_path}\")\n",
    "    if progress_callback:\n",
    "        progress_callback(100, True)\n",
    "\n",
    "def load_config(config_path='config.yaml'):\n",
    "    try:\n",
    "        with open(config_path, 'r', encoding='utf-8') as f:\n",
    "            return yaml.safe_load(f)\n",
    "    except FileNotFoundError:\n",
    "        logging.warning(\"配置文件未找到，使用默认值\")\n",
    "        return {\n",
    "            \"scale_min\": 0.5,\n",
    "            \"scale_max\": 1.5,\n",
    "            \"num_images\": 10,\n",
    "            \"output_type\": \"RGB\"\n",
    "        }\n",
    "    except yaml.YAMLError as e:\n",
    "        logging.error(f\"配置文件解析错误: {e}\")\n",
    "        return {}\n",
    "\n",
    "class ImageProcessorApp:\n",
    "    def __init__(self, root):\n",
    "        self.root = root\n",
    "        self.root.title(\"图像处理器\")\n",
    "        self.root.geometry(\"600x500\")\n",
    "        self.root.resizable(True, True)\n",
    "        \n",
    "        # 设置中文字体\n",
    "        self.style = ttk.Style()\n",
    "        self.style.configure('.', font=('SimHei', 10))\n",
    "        \n",
    "        # 配置变量\n",
    "        self.config = load_config()\n",
    "        self.scale_min_var = tk.DoubleVar(value=self.config.get(\"scale_min\", 0.5))\n",
    "        self.scale_max_var = tk.DoubleVar(value=self.config.get(\"scale_max\", 1.5))\n",
    "        self.num_images_var = tk.IntVar(value=self.config.get(\"num_images\", 10))\n",
    "        self.output_type_var = tk.StringVar(value=self.config.get(\"output_type\", \"RGB\"))\n",
    "        self.zip_path = tk.StringVar()\n",
    "        \n",
    "        # 线程和事件\n",
    "        self.processing_thread = None\n",
    "        self.stop_event = threading.Event()\n",
    "        \n",
    "        self.create_widgets()\n",
    "    \n",
    "    def create_widgets(self):\n",
    "        # 创建主框架\n",
    "        main_frame = ttk.Frame(self.root, padding=\"20\")\n",
    "        main_frame.pack(fill=tk.BOTH, expand=True)\n",
    "        \n",
    "        # 文件选择\n",
    "        file_frame = ttk.LabelFrame(main_frame, text=\"文件选择\", padding=\"10\")\n",
    "        file_frame.pack(fill=tk.X, pady=5)\n",
    "        \n",
    "        ttk.Label(file_frame, text=\"ZIP 文件:\").grid(row=0, column=0, sticky=tk.W, pady=5)\n",
    "        ttk.Entry(file_frame, textvariable=self.zip_path, width=40).grid(row=0, column=1, padx=5, pady=5)\n",
    "        ttk.Button(file_frame, text=\"浏览...\", command=self.browse_file).grid(row=0, column=2, padx=5, pady=5)\n",
    "        \n",
    "        # 参数设置\n",
    "        params_frame = ttk.LabelFrame(main_frame, text=\"参数设置\", padding=\"10\")\n",
    "        params_frame.pack(fill=tk.X, pady=5)\n",
    "        \n",
    "        # 最小缩放比例\n",
    "        ttk.Label(params_frame, text=\"最小缩放比例:\").grid(row=0, column=0, sticky=tk.W, pady=5)\n",
    "        scale_min_slider = ttk.Scale(\n",
    "            params_frame, \n",
    "            variable=self.scale_min_var,\n",
    "            from_=0.1, \n",
    "            to=1.0, \n",
    "            orient=tk.HORIZONTAL, \n",
    "            length=300,\n",
    "            command=lambda s: self.scale_min_var.set(round(float(s), 1))\n",
    "        )\n",
    "        scale_min_slider.grid(row=0, column=1, padx=5, pady=5)\n",
    "        ttk.Label(params_frame, textvariable=self.scale_min_var).grid(row=0, column=2, padx=5, pady=5)\n",
    "        \n",
    "        # 最大缩放比例\n",
    "        ttk.Label(params_frame, text=\"最大缩放比例:\").grid(row=1, column=0, sticky=tk.W, pady=5)\n",
    "        scale_max_slider = ttk.Scale(\n",
    "            params_frame, \n",
    "            variable=self.scale_max_var,\n",
    "            from_=1.0, \n",
    "            to=3.0, \n",
    "            orient=tk.HORIZONTAL, \n",
    "            length=300,\n",
    "            command=lambda s: self.scale_max_var.set(round(float(s), 1))\n",
    "        )\n",
    "        scale_max_slider.grid(row=1, column=1, padx=5, pady=5)\n",
    "        ttk.Label(params_frame, textvariable=self.scale_max_var).grid(row=1, column=2, padx=5, pady=5)\n",
    "        \n",
    "        # 生成图像数量\n",
    "        ttk.Label(params_frame, text=\"生成图像数量:\").grid(row=2, column=0, sticky=tk.W, pady=5)\n",
    "        num_images_slider = ttk.Scale(\n",
    "            params_frame, \n",
    "            variable=self.num_images_var,\n",
    "            from_=2, \n",
    "            to=20, \n",
    "            orient=tk.HORIZONTAL, \n",
    "            length=300,\n",
    "            command=lambda s: self.num_images_var.set(round(float(s)))\n",
    "        )\n",
    "        num_images_slider.grid(row=2, column=1, padx=5, pady=5)\n",
    "        ttk.Label(params_frame, textvariable=self.num_images_var).grid(row=2, column=2, padx=5, pady=5)\n",
    "        \n",
    "        # 输出类型\n",
    "        ttk.Label(params_frame, text=\"输出类型:\").grid(row=3, column=0, sticky=tk.W, pady=5)\n",
    "        output_types = [\"RGB\", \"Grayscale\", \"Binary\"]\n",
    "        ttk.Combobox(params_frame, textvariable=self.output_type_var, values=output_types, state=\"readonly\").grid(row=3, column=1, padx=5, pady=5)\n",
    "        \n",
    "        # 进度条\n",
    "        progress_frame = ttk.LabelFrame(main_frame, text=\"处理进度\", padding=\"10\")\n",
    "        progress_frame.pack(fill=tk.X, pady=5)\n",
    "        \n",
    "        self.progress_var = tk.IntVar(value=0)\n",
    "        self.progress_bar = ttk.Progressbar(\n",
    "            progress_frame, \n",
    "            variable=self.progress_var, \n",
    "            maximum=100, \n",
    "            length=500\n",
    "        )\n",
    "        self.progress_bar.pack(fill=tk.X, padx=5, pady=5)\n",
    "        \n",
    "        self.status_label = ttk.Label(progress_frame, text=\"就绪\")\n",
    "        self.status_label.pack(anchor=tk.W, padx=5, pady=5)\n",
    "        \n",
    "        # 按钮\n",
    "        button_frame = ttk.Frame(main_frame, padding=\"10\")\n",
    "        button_frame.pack(fill=tk.X, pady=5)\n",
    "        \n",
    "        self.process_button = ttk.Button(button_frame, text=\"开始处理\", command=self.start_processing)\n",
    "        self.process_button.pack(side=tk.LEFT, padx=5)\n",
    "        \n",
    "        self.stop_button = ttk.Button(button_frame, text=\"停止\", command=self.stop_processing, state=tk.DISABLED)\n",
    "        self.stop_button.pack(side=tk.LEFT, padx=5)\n",
    "    \n",
    "    def browse_file(self):\n",
    "        filename = filedialog.askopenfilename(\n",
    "            title=\"选择ZIP文件\",\n",
    "            filetypes=[(\"ZIP文件\", \"*.zip\")]\n",
    "        )\n",
    "        if filename:\n",
    "            self.zip_path.set(filename)\n",
    "    \n",
    "    def start_processing(self):\n",
    "        zip_file = self.zip_path.get()\n",
    "        if not zip_file:\n",
    "            messagebox.showerror(\"错误\", \"请选择ZIP文件\")\n",
    "            return\n",
    "        \n",
    "        if not os.path.exists(zip_file):\n",
    "            messagebox.showerror(\"错误\", \"文件不存在\")\n",
    "            return\n",
    "        \n",
    "        # 更新配置\n",
    "        self.config = {\n",
    "            \"scale_min\": self.scale_min_var.get(),\n",
    "            \"scale_max\": self.scale_max_var.get(),\n",
    "            \"num_images\": self.num_images_var.get(),\n",
    "            \"output_type\": self.output_type_var.get()\n",
    "        }\n",
    "        \n",
    "        # 保存配置\n",
    "        try:\n",
    "            with open('config.yaml', 'w', encoding='utf-8') as f:\n",
    "                yaml.dump(self.config, f)\n",
    "        except Exception as e:\n",
    "            logging.error(f\"保存配置失败: {e}\")\n",
    "        \n",
    "        # 重置进度\n",
    "        self.progress_var.set(0)\n",
    "        self.status_label.config(text=\"处理中...\")\n",
    "        \n",
    "        # 禁用/启用按钮\n",
    "        self.process_button.config(state=tk.DISABLED)\n",
    "        self.stop_button.config(state=tk.NORMAL)\n",
    "        \n",
    "        # 重置停止事件并启动处理线程\n",
    "        self.stop_event.clear()\n",
    "        self.processing_thread = threading.Thread(\n",
    "            target=process_images_from_zip,\n",
    "            kwargs={\n",
    "                'zip_path': zip_file,\n",
    "                'scale_min': self.scale_min_var.get(),\n",
    "                'scale_max': self.scale_max_var.get(),\n",
    "                'num_images': self.num_images_var.get(),\n",
    "                'output_type': self.output_type_var.get(),\n",
    "                'progress_callback': self.update_progress,\n",
    "                'thread_event': self.stop_event\n",
    "            },\n",
    "            daemon=True\n",
    "        )\n",
    "        self.processing_thread.start()\n",
    "    \n",
    "    def stop_processing(self):\n",
    "        self.stop_event.set()\n",
    "        self.status_label.config(text=\"正在停止...\")\n",
    "    \n",
    "    def update_progress(self, value, completed=False):\n",
    "        self.progress_var.set(value)\n",
    "        if completed:\n",
    "            self.status_label.config(text=\"处理完成\")\n",
    "            self.process_button.config(state=tk.NORMAL)\n",
    "            self.stop_button.config(state=tk.DISABLED)\n",
    "            messagebox.showinfo(\"成功\", \"图像处理完成！\")\n",
    "        else:\n",
    "            self.status_label.config(text=f\"处理中: {value}%\")\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    root = tk.Tk()\n",
    "    app = ImageProcessorApp(root)\n",
    "    root.mainloop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a3ca4ea-1ae7-4069-95e7-11d7b4eceb2a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
